package framework;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openems.entity.Entity;
import org.openems.entity.Key;
import org.openems.entity.ListEntity;

import framework.webclient.EntityQueue;
import framework.webclient.EntitySocket;
import framework.webclient.HttpEntitySocket;
import framework.webclient.ProxyData;
import framework.webclient.RestSocket;
import framework.webclient.RestSocketImpl;
import framework.webclient.SystemProperty;
import framework.webclient.google.GoogleAuthentication;

public abstract class AbstractDomainService implements DomainService {
	public SilentAccessor getSilentAccessor() {
		return silentAccessor;
	}

	public ActiveAccessor getActiveAccessor() {
		return accessor;
	}

	public RestSocket getRestSocket() {
		return restSocket;
	}
		
	private void buildID() {
		Object object = getID();
		ID id = (ID)object;
		id.buildID(getApplicationName());
	}

	private String getApplicationName() {
		return this.getSystemProperty().getApplicationType();
	}
	
	@Override
	public void initialize(String account, String password, ProxyData proxy) {
		buildID();
		restSocket = createRestSocket(account, password, proxy);
		entitySocket = new EntitySocket(restSocket, "parameter", getSystemProperty());

		this.addCommand(clientManager);
		this.addCommand(new EntityUploader(new EntityQueue(entitySocket), accessor));
		
		registerDefaultEntities(new EntityResigter() {
			@Override
			public void registerEntity(Entity entity) {
				entities.put(entity.getKey(), entity);
			}
		});
		
		syncFromServer(entitySocket);
		initialize();
	}

	protected RestSocket createRestSocket(String account, String password, ProxyData proxy) {
		GoogleAuthentication auth = new GoogleAuthentication("http://restmiyake.appspot.com/", account, password, proxy);
		return new RestSocketImpl(Utility.convertCookie(auth.getAuthCookie()), "http://restmiyake.appspot.com/resources/");
	}

	abstract protected Object getID();

	public EntitySocket getEntitySocket() {
		return entitySocket;
	}

	@Override
	public boolean isOnline() {
		return true;
	}

	private Map<Key, Entity> entities = new HashMap<Key, Entity>();
	private List<Command> commands = new ArrayList<Command>();
	private Resolver resolver = new NullResolver();
	private SilentAccessor silentAccessor = new SilentAccessor();
	private ActiveAccessor accessor = new ActiveAccessor();
	
	private ClientManager clientManager = new ClientManagerJavafx(accessor);
	
	public class ActiveAccessor implements Accessor {
		@Override
		public Entity get(Key key) {
			return AbstractDomainService.this.getEntity(key);
			//return entities.get(key);
		}

		@Override
		public void put(Entity entity) {
			AbstractDomainService.this.putEntity(entity);
		}

		public void addEntityStoreListener(
				EntityStoreListener entityStoreListener, Key key) {
			AbstractDomainService.this.addEntityStoreListener(entityStoreListener, key);
		}

		public void removeEntityStoreListener(
				EntityStoreListener entityStoreListener) {
			AbstractDomainService.this.removeEntityStoreListener(entityStoreListener);
		}

	};
	
	public class SilentAccessor implements Accessor {
		private List<Key> changeKeys = new ArrayList<Key>();
		
		public Entity get(Key key) {
			return AbstractDomainService.this.getEntity(key);
		}
		
		public void put(Entity entity) {
			entities.put(entity.getKey(), entity);
			changeKeys.add(entity.getKey());
		}
		
		public List<Key> getChangedKeys() {
			return this.changeKeys;
		}

		public void clear() {
			this.changeKeys.clear();
		}
	}

	private RestSocket restSocket;
	private EntitySocket entitySocket;
	
	abstract protected SystemProperty getSystemProperty();
	abstract protected void initialize();
	
	private void syncFromServer(HttpEntitySocket httpSocket) {
		List<Entity> listElements = new ArrayList<Entity>();
		
		for (Key key : this.entities.keySet()) {
			if (!this.getEntity(key).isPersistable()) {
				continue;
			}
			Entity storedEntity = httpSocket.download(key);
			
			if (storedEntity != null) {
				storedEntity.setKey(key);
				entities.put(key, storedEntity);
				
				if (storedEntity instanceof ListEntity) {
					for (Key k : ((ListEntity)storedEntity).getKeys()) {
						listElements.add(httpSocket.download(k));
					}
				}
			}
		}
		
		for (Entity e: listElements) {
			this.entities.put(e.getKey(), e);
		}
	}

	abstract protected void registerDefaultEntities(EntityResigter defaultSetter);

	/* (non-Javadoc)
	 * @see framework.EntityStore#put(framework.entity.Entity)
	 */
	@Override
	public void putEntity(Entity newEntity) {
		if (newEntity.getKey() == null) { // Dynamically created entity
			newEntity.setKey(this.createKey(newEntity));
		}
		Entity oldEntity = getOldClone(newEntity);
		if (oldEntity != null && oldEntity.equals(newEntity)) {
			return; // do nothing if nothing changed
		}
		this.entities.put(newEntity.getKey(), newEntity);
		List<Key> resolvedKeys = new ArrayList<Key>(Arrays.asList(newEntity.getKey())); 
		
		silentAccessor.clear();
		resolver.resolve(silentAccessor, oldEntity, newEntity);
		resolvedKeys.addAll(silentAccessor.getChangedKeys());
		
		executeCommands(resolvedKeys, newEntity.getKey());
	}

	private Entity getOldClone(Entity newEntity) {
		Entity old = this.entities.get(newEntity.getKey());
		if (old == null) {
			return null;
		}
		Entity oldEntity = old.getClone();
		return oldEntity;
	}
	
	private Key createKey(Entity entity) {
		return new Key(entity.getClass().getSimpleName()+entity.getName()+String.valueOf(entity.hashCode()), getApplicationName());
	}

	protected void executeCommands(List<Key> keys, Key key) {
		for (Command command : commands) {
			command.execute(keys, key);
		}
	}

	/* (non-Javadoc)
	 * @see framework.EntityStore#get(framework.entity.Key)
	 */
	@Override
	public Entity getEntity(Key key) {
		Entity entity = this.entities.get(key);
		return entity.getClone();
		
	}
	
	/* (non-Javadoc)
	 * @see framework.EntityStore#addCommand(framework.Command)
	 */
	@Override
	public void addCommand(Command command) {
		this.commands.add(command);
	}

	protected void setDependencyResolver(Resolver resolver2) {
		this.resolver = resolver2;
	}


	/* (non-Javadoc)
	 * @see framework.EntityStore#addEntityStoreListener(framework.EntityStoreListener, framework.entity.Key)
	 */
	@Override
	public void addEntityStoreListener(EntityStoreListener entityStoreListener, Key key) {
		clientManager.addListener(entityStoreListener, key);
	}


	/* (non-Javadoc)
	 * @see framework.EntityStore#removeEntityStoreListener(framework.EntityStoreListener)
	 */
	@Override
	public void removeEntityStoreListener(
			EntityStoreListener entityStoreListener) {
		clientManager.removeListener(entityStoreListener);
	}

}

